/*
	Copyright (c) 2004-2006, The Dojo Foundation
	All Rights Reserved.

	Licensed under the Academic Free License version 2.1 or above OR the
	modified BSD license. For more information on Dojo licensing, see:

		http://dojotoolkit.org/community/licensing.shtml
*/

dojo.provide("dojo.lang.declare");

dojo.require("dojo.lang.common");
dojo.require("dojo.lang.extras");

dojo.lang.declare = function(	/*String*/ className, 
								/*Function|Array*/ superclass, 
								/*Function?*/ init, 
								/*Object|Array*/ props){
	/*
	 *	summary: Create a feature-rich constructor with a compact notation
	 *	className: the name of the constructor (loosely, a "class")
	 * 	superclass:
	 *		may be a Function, or an Array of Functions. If "superclass" is an
	 *		array, the first element is used as the prototypical ancestor and
	 *		any following Functions become mixin ancestors.
	 *	init: an initializer function
	 *	props:
	 *		an object (or array of objects) whose properties are copied to the
	 *		created prototype
	 *	description:
	 *		Create a constructor using a compact notation for inheritance and
	 *		prototype extension. "superclass" argument may be a Function, or an
	 *		array of Functions. 
	 *
	 *		If "superclass" is an array, the first element is used as the
	 *		prototypical ancestor and any following Functions become mixin
	 *		ancestors. 
	 * 
	 *		All "superclass(es)" must be Functions (not mere Objects).
	 *
	 *		Using mixin ancestors provides a type of multiple inheritance.
	 *		Mixin ancestors prototypical properties are copied to the subclass,
	 *		and any inializater/constructor is invoked. 
	 *
	 *		Properties of object "props" are copied to the constructor
	 *		prototype. If "props" is an array, properties of each object in the
	 *		array are copied to the constructor prototype.
	 *
	 *		name of the class ("className" argument) is stored in
	 *		"declaredClass" property
	 * 
	 *		Initializer functions are called when an object is instantiated
	 *		from this constructor.
	 * 
	 *		Aliased as "dojo.declare"
	 *
	 * Usage:
	 *
	 *		dojo.declare("my.classes.bar", my.classes.foo,
	 *			function(){
	 *				// initialization function
	 *				this.myComplicatedObject = new ReallyComplicatedObject(); 
	 *			},
	 *			{ // properties to be added to the class prototype
	 *				someValue: 2,
	 *				someMethod: function(){ 
	 *					doStuff(); 
	 *				}
	 *			}
	 *		);
	 *
	 */
	if((dojo.lang.isFunction(props))||((!props)&&(!dojo.lang.isFunction(init)))){ 
		// parameter juggling to support omitting init param (also allows
		// reordering init and props arguments)
		var temp = props;
		props = init;
		init = temp;
	}	
	var mixins = [ ];
	if(dojo.lang.isArray(superclass)){
		mixins = superclass;
		superclass = mixins.shift();
	}
	if(!init){
		init = dojo.evalObjPath(className, false);
		if((init)&&(!dojo.lang.isFunction(init))){ init = null };
	}
	var ctor = dojo.lang.declare._makeConstructor();
	var scp = (superclass ? superclass.prototype : null);
	if(scp){
		scp.prototyping = true;
		ctor.prototype = new superclass();
		scp.prototyping = false; 
	}
	ctor.superclass = scp;
	ctor.mixins = mixins;
	for(var i=0,l=mixins.length; i<l; i++){
		dojo.lang.extend(ctor, mixins[i].prototype);
	}
	ctor.prototype.initializer = null;
	ctor.prototype.declaredClass = className;
	if(dojo.lang.isArray(props)){
		dojo.lang.extend.apply(dojo.lang, [ctor].concat(props));
	}else{
		dojo.lang.extend(ctor, (props)||{});
	}
	dojo.lang.extend(ctor, dojo.lang.declare._common);
	ctor.prototype.constructor = ctor;
	ctor.prototype.initializer = (ctor.prototype.initializer)||(init)||(function(){});
	var created = dojo.parseObjPath(className, null, true);
	created.obj[created.prop] = ctor;
	return ctor; // Function
}

dojo.lang.declare._makeConstructor = function(){
	return function(){ 
		// get the generational context (which object [or prototype] should be constructed)
		var self = this._getPropContext();
		var s = self.constructor.superclass;
		if((s)&&(s.constructor)){
			if(s.constructor==arguments.callee){
				// if this constructor is invoked directly (my.ancestor.call(this))
				this._inherited("constructor", arguments);
			}else{
				this._contextMethod(s, "constructor", arguments);
			}
		}
		var ms = (self.constructor.mixins)||([]);
		for(var i=0, m; (m=ms[i]); i++) {
			(((m.prototype)&&(m.prototype.initializer))||(m)).apply(this, arguments);
		}
		if((!this.prototyping)&&(self.initializer)){
			self.initializer.apply(this, arguments);
		}
	}
}

dojo.lang.declare._common = {
	_getPropContext: function(){ return (this.___proto||this); },
	// caches ptype context and calls method on it
	_contextMethod: function(ptype, method, args){
		var result, stack = this.___proto;
		this.___proto = ptype;
		try { result = ptype[method].apply(this,(args||[])); }
		catch(e) { throw e; }	
		finally { this.___proto = stack; }
		return result;
	},
	_inherited: function(prop, args){
		// summary:
		//		Searches backward thru prototype chain to find nearest
		//		ancestral instance of prop. Internal use only.
		var p = this._getPropContext();
		do{
			if((!p.constructor)||(!p.constructor.superclass)){ return; }
			p = p.constructor.superclass;
		}while(!(prop in p));
		return (dojo.lang.isFunction(p[prop]) ? this._contextMethod(p, prop, args) : p[prop]);
	},
	inherited: function(prop, args){
		dojo.deprecated("'inherited' method is dangerous, do not up-call! 'inherited' is slated for removal in 0.5; name your super class (or use superclass property) instead.", "0.5");
		this._inherited(prop, args);
	}
}

dojo.declare = dojo.lang.declare;
